<?php
/**
 * Created by PhpStorm.
 * User: xiong <1099446868@qq.com>
 * DateTime: 2019/3/13
 * TIME: 22:21
 */

namespace app\Console;

use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\console\input\Argument;
use think\console\input\Option;
use think\Db;
use think\exception\ErrorException;
use think\facade\Env;

//采集章节标题和章节网址(不包括章节内容)
class GetCon extends Command {

    public $max_precess = 4;
    public $mpid = 0;
    public $works = [];
    public $new_index = 0;
    public $fields = [  //这些字段每个页面有很多个,需要循环采集  这个属性中包含两个字段   title和url
                        'title' => [
                            'select' => '//*[@id="list"]/dl/dd/a',
                            'node'   => 'nodeValue',
                        ],
    ];

    protected function configure() {
        $this->setName('getcon')
            ->addArgument('id', Argument::OPTIONAL, "book name id")
            ->addArgument('ided', Argument::OPTIONAL, "book name ided")
            ->addOption('debug', null, Option::VALUE_REQUIRED, 'debug option')
            ->setDescription('get books content');
    }

    protected function execute(Input $input, Output $output) {
        throw new \Exception('本任务已完成,请勿重复采集');
        $redis = new \Redis();
        $redis->connect('127.0.0.1', 6379);
        if ($redis->lLen('book') != 0) {
            $output->writeln('队列中存在未完成任务' . $redis->lLen('book'));
            $output->writeln('继续中...');
            $redis->flushAll('book');exit;
        } else {
            $id = trim($input->getArgument('id'));  //从此开始,包括id
            $ided = trim($input->getArgument('ided')); //到此结束,要求不包括本身
            $ided = $ided - 1;
//            $output->writeln($id);
//            $output->writeln($ided);
            if (empty($id) or empty($ided)) {
                $output->writeln('parameter 1 and 2 must have');
                exit;
            }
            if($input->hasOption('debug')){
                $bug = $input->getOption('debug');
                if($bug == 1){
                    //调试文件 title.php,内容是由于网络等原因没采集到内容的书的id
                    $file = Env::get('app_path') . 'console/debug/title.php';
                    if(file_exists($file)){
                        $ids = include $file;
                        $books = Db::name('bookname')->field('id,bookname,url,ctable')->where('id', 'in', $ids)->select();
                    }else{
                        $output->writeln('debug file not exists');
                        exit;
                    }
                }
            }else{
                $books = Db::name('bookname')->field('id,bookname,url,ctable')->where('id', 'between', [$id, $ided])->select();
            }
            foreach ($books as $v) {
                $redis->lPush('book', json_encode($v));
            }
        }

        $redis->close();
        swoole_set_process_name('php-think:con');
        $this->mpid = posix_getpid();
        $this->Prun();
        $this->processWait();

        $output->writeln('get start');
    }

    public function Prun() {
        for ($i = 0; $i < $this->max_precess; $i++) {
            $this->CreateProcess();
        }
    }

    public function CreateProcess($index = null) {
        $process = new \swoole_process(function (\swoole_process $worker) use ($index) {
            if (is_null($index)) {
                $index = $this->new_index;
                $this->new_index++;
            }
            $redis = new \Redis();
            $redis->connect('127.0.0.1', '6379', 2);
            swoole_set_process_name(sprintf('php-think:son-%s', $this->new_index));
            for ($j = 0; $j < 10000; $j++) {
                $this->checkMpid($worker); //当前进程pid
                try {
                    $redis->ping();
                } catch (\RedisException $e) {
                    echo $e->getMessage();
                    exit;
                }
                if ($redis->lLen('book') != 0) {
                    $v = $redis->rPop('book');
                } else {
                    dump("endTime: " . date("H:i:s") . 'maybe error or finished');
                    exit;
                }
                if (empty($v)) {
                    exit;
                }
                $v = json_decode($v, true);
                $html = curl_get($v['url']);
                $title = $this->getTitle($html, $this->fields, $v['bookname'], $v['id']);
                $app_path = Env::get('app_path');
                if (empty($title)) {    //没爬到数据
                    file_put_contents($app_path . 'console/debug/title2.php', $v['id'] . ',', FILE_APPEND);
                    echo 'gettitle error - ' . $v['id'] . PHP_EOL;
                    continue;
                }
                $table = 'book_con_' . $v['ctable'];
                $sql = "insert into {$table}(bid,bookname,`title`,url) values" . $title;
                try {
                    Db::execute('set names utf8');
                    Db::execute($sql);
                } catch (\PDOException $e) {   //爬到的数据组成sql有误，插入不成功
                    file_put_contents($app_path . 'console/debug/title2.php', $v['id'] . ',', FILE_APPEND);
                    echo 'insert error - ' . $v['id'] . PHP_EOL;
                }

                echo $v['id'] . PHP_EOL;
            }
            //exit('进程任务数量不足');
        }, false, false);
        $pid = $process->start();
        $this->works[$index] = $pid;
        return $pid;
    }

    public function getTitle(string $html, array $fields = [], $bookname, $id) {
        $return = [];
        $document = new \DOMDocument();
        @$document->loadHTML($html);
        $xpath = new \DOMXPath($document);
        if (!empty($fields)) {
            $sql3 = '';
            foreach ($fields as $fields_k => $fields_v) {
                $elements_s[$fields_k] = $xpath->query($fields_v['select']);

                if ($elements_s[$fields_k]->length != 0) {
                    for ($i = 0; $i < $elements_s[$fields_k]->length; $i++) {
                        $title = $elements_s[$fields_k][$i]->nodeValue;
                        $title = addslashes($title);
                        $url = $elements_s[$fields_k][$i]->attributes[0]->nodeValue;

                        //$sql3 .= "($id .'".$bookname ."','".$title."','".$url."'),";
                        $sql3 .= '(' . $id . ',"' . $bookname . '","' . $title . '","' . $url . '"),';
                        //$sql3 .= "('".$bookname ."','".$title."','".$url."'),";
                    }
                    $sql3 = rtrim($sql3, ',');
                }
            }
            //不想直接爬取内容页，先记录内容页url

        }
        return $sql3;

    }

    public function checkMpid(&$worker) {
        if (!\swoole_process::kill($this->mpid, 0)) {
            // 这句提示,实际是看不到的.需要写到日志中
            $log = "endTime: " . date("H:i:s") . "--Master process exited, I [{$worker->pid}] also quit\n";
            echo $log;
            $worker->exit();

        }
    }

    public function rebootProcess($ret) {
        $pid = $ret['pid'];
        $index = array_search($pid, $this->works);
        if ($index !== false) {
            $index = intval($index);
            $new_pid = $this->CreateProcess($index);
            echo "rebootProcess: {$index}={$new_pid} Done\n";
            return;
        }
        throw new \Exception('rebootProcess Error: no pid');
    }

//清理僵尸进程

    public function processWait() {
        while (1) {
            if (count($this->works)) {
                $ret = \swoole_process::wait();
                if ($ret) {
                    $this->rebootProcess($ret);
                }
            } else {
                break;
            }
        }
    }
}